#include "pch.hpp"
#include "Frame.hpp"

using namespace Engine;
using namespace Engine::Ui;




Frame::Frame(Widget::weak_ptr parent)
	: Widget(parent)
	, m_hoveredBorder(Border::Center)
{
	stateChanged.connect(boost::bind(&Frame::onStateChanged, this, _1));
}
///////////////////////////////////////////////////////////////////////////////////////////////////




int4 Frame::padding() const
{
	return m_padding;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

Border::Type Frame::hoveredBorder() const
{
	return m_hoveredBorder;
}
///////////////////////////////////////////////////////////////////////////////////////////////////




int2 Frame::extends() const
{
	return m_extends;
}
///////////////////////////////////////////////////////////////////////////////////////////////////




int4 Frame::calculateExtends(const GlyphMap& glyphs)
{
	// There are some simple rules:
	// The sizes of the glyphs have to match (when present):
	int4 debug;
	boost::optional<int> left;
	boost::optional<int> top;
	boost::optional<int> right;
	boost::optional<int> bottom;
	GlyphMap::const_iterator i = glyphs.end();

	if((i = glyphs.find(Border::TopLeft)) != glyphs.end())
	{
		const Glyph& glyph = i->second;
		left = glyph.size().width;
		top  = glyph.size().height;

		debug.x = *left;
		debug.y = *top;
	}

	if((i = glyphs.find(Border::Top)) != glyphs.end())
	{
		const Glyph& glyph = i->second;
		if(top && *top != glyph.size().height) BOOSTEXT_THROW(exception("Border::Top size does not match the rest"));
		else top = glyph.size().height;
	}

	if((i = glyphs.find(Border::TopRight)) != glyphs.end())
	{
		const Glyph& glyph = i->second;
		if(top && *top != glyph.size().height) BOOSTEXT_THROW(exception("Border::TopRight size does not match the rest"));
		else top = glyph.size().height;
		right = glyph.size().width;
	}

	if((i = glyphs.find(Border::Right)) != glyphs.end())
	{
		const Glyph& glyph = i->second;
		if(right && *right != glyph.size().width) BOOSTEXT_THROW(exception("Border::Right size does not match the rest"));
		else right = glyph.size().width;
	}

	if((i = glyphs.find(Border::BottomRight)) != glyphs.end())
	{
		const Glyph& glyph = i->second;
		if(right && *right != glyph.size().width) BOOSTEXT_THROW(exception("Border::BottomRight size does not match the rest"));
		else right = glyph.size().width;
		bottom = glyph.size().height;

		debug.z = *right;
		debug.w = *bottom;
	}

	if((i = glyphs.find(Border::Bottom)) != glyphs.end())
	{
		const Glyph& glyph = i->second;
		if(bottom && *bottom != glyph.size().height) BOOSTEXT_THROW(exception("Border::Bottom size does not match the rest"));
		else bottom = glyph.size().height;
	}

	if((i = glyphs.find(Border::BottomLeft)) != glyphs.end())
	{
		const Glyph& glyph = i->second;
		if(bottom && *bottom != glyph.size().height) BOOSTEXT_THROW(exception("Border::BottomLeft size does not match the rest"));
		else bottom = glyph.size().height;
		left = glyph.size().width;
	}

	if((i = glyphs.find(Border::Left)) != glyphs.end())
	{
		const Glyph& glyph = i->second;
		if(left && *left != glyph.size().width) BOOSTEXT_THROW(exception("Border::Left size does not match the rest"));
		else left = glyph.size().width;
	}

	return int4(
		(left) ? *left : 0,
		(top) ? *top : 0,
		(right) ? *right : 0,
		(bottom) ? *bottom : 0);
}
///////////////////////////////////////////////////////////////////////////////////////////////////




Widgets::Type Frame::type() const
{
	return Widgets::Frame;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void Frame::onStateChanged(Widget::weak_ptr that)
{
	auto _that = that.lock();

	if(_that && shared_from_this() == _that)
	{
		m_backgroundGlyphs.clear();

		auto s = style();
		if(s->hasAttribute(Attributes::BackgroundGlyphTopLeft, state()))
			m_backgroundGlyphs.insert(std::make_pair(Border::TopLeft, s->attribute<Glyph>(Attributes::BackgroundGlyphTopLeft, state())));
		if(s->hasAttribute(Attributes::BackgroundGlyphTop, state()))
			m_backgroundGlyphs.insert(std::make_pair(Border::Top, s->attribute<Glyph>(Attributes::BackgroundGlyphTop, state())));
		if(s->hasAttribute(Attributes::BackgroundGlyphTopRight, state()))
			m_backgroundGlyphs.insert(std::make_pair(Border::TopRight, s->attribute<Glyph>(Attributes::BackgroundGlyphTopRight, state())));
		if(s->hasAttribute(Attributes::BackgroundGlyphRight, state()))
			m_backgroundGlyphs.insert(std::make_pair(Border::Right, s->attribute<Glyph>(Attributes::BackgroundGlyphRight, state())));
		if(s->hasAttribute(Attributes::BackgroundGlyphBottomRight, state()))
			m_backgroundGlyphs.insert(std::make_pair(Border::BottomRight, s->attribute<Glyph>(Attributes::BackgroundGlyphBottomRight, state())));
		if(s->hasAttribute(Attributes::BackgroundGlyphBottom, state()))
			m_backgroundGlyphs.insert(std::make_pair(Border::Bottom, s->attribute<Glyph>(Attributes::BackgroundGlyphBottom, state())));
		if(s->hasAttribute(Attributes::BackgroundGlyphBottomLeft, state()))
			m_backgroundGlyphs.insert(std::make_pair(Border::BottomLeft, s->attribute<Glyph>(Attributes::BackgroundGlyphBottomLeft, state())));
		if(s->hasAttribute(Attributes::BackgroundGlyphLeft, state()))
			m_backgroundGlyphs.insert(std::make_pair(Border::Left, s->attribute<Glyph>(Attributes::BackgroundGlyphLeft, state())));

		m_padding = calculateExtends(m_backgroundGlyphs);
		m_extends = int2(m_padding.x + m_padding.z, m_padding.y + m_padding.w);
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void Frame::update(NestedState& state)
{
	Widget::update(state);

	// Test if the mouse hovers over the frame only (not the client area.
	// We can simply do this by excluding the client area
	if(mouseHovered() && !clientRect().intersects(state.cursor().pos()))
	{
		m_hoveredBorder = hitTest(m_backgroundGlyphs, state);
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void Frame::renderAdditional(Quads& quads, const NestedState& state)
{
	auto screenPos = pos() + state.parentPos();
	auto cSize     = clientSize();

	// The frame is rendered in 2 goes
	// The first one is to render the background of the frame
	// in the background color of the widget (needed for a rounded shape)
	// and the second one renders the actual border of the frame
	renderFrame(quads, screenPos, cSize, m_backgroundGlyphs, m_padding, backgroundColor());
}
///////////////////////////////////////////////////////////////////////////////////////////////////

uint32 Frame::numAdditionalQuads() const
{
	uint32 num = __super::numAdditionalQuads();
	num += static_cast<uint32>(m_backgroundGlyphs.size());
	return num;
}
///////////////////////////////////////////////////////////////////////////////////////////////////




void Frame::renderFrame(Quads& quads, const int2& screenPos, const intsize2& clientSize, const GlyphMap& glyphs, const int4& extends, const math::color& color) const
{
	foreach(const GlyphPair& p, glyphs)
	{
		// If there is a center glyph then we don't render it: this is done
		// by the Widget class
		if(p.first == Border::Center)
			continue;

		const Glyph& glyph = p.second;
		Quad quad = glyph.quad();
		math::rectangle<int> rect = frameRect(p.first, clientSize, extends);
		rect.move(screenPos);
		quad.resize(rect);
		quad.setColor(color);
		quads.push_back(quad);
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////

Border::Type Frame::hitTest(const GlyphMap& glyphs, const NestedState& state) const
{
	foreach(const GlyphPair& p, glyphs)
	{
		if(p.first == Border::Center)
			continue;

		math::rectangle<int> rect = frameRect(p.first, clientSize(), padding());
		rect.move(pos());
		if(rect.intersects(state.cursor().pos()))
			return p.first;
	}

	return Border::Center;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

math::rectangle<int> Frame::frameRect(Border::Type border, const intsize2& clientSize, const int4& extends) const
{
	int left   = extends.x;
	int top    = extends.y;
	int right  = extends.z;
	int bottom = extends.w;

	switch(border)
	{
	case Border::Left:
		return math::rectangle<int>::pointSize(int2(0, top), intsize2(left, clientSize.height));
	case Border::Top:
		return math::rectangle<int>::pointSize(int2(left, 0), intsize2(clientSize.width, top));
	case Border::Right:
		return math::rectangle<int>::pointSize(int2(left + clientSize.width, top), intsize2(right, clientSize.height));
	case Border::Bottom:
		return math::rectangle<int>::pointSize(int2(left, top + clientSize.height), intsize2(clientSize.width, bottom));
	case Border::TopLeft:
		return math::rectangle<int>::pointSize(int2(0, 0), intsize2(left, top));
	case Border::TopRight:
		return math::rectangle<int>::pointSize(int2(left + clientSize.width, 0), intsize2(right, top));
	case Border::BottomRight:
		return math::rectangle<int>::pointSize(int2(left + clientSize.width, top + clientSize.height), intsize2(right, bottom));
	case Border::BottomLeft:
		return math::rectangle<int>::pointSize(int2(0, top + clientSize.height), intsize2(left, bottom));
	default:
		BOOSTEXT_THROW(invalid_value_exception(border));
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////

int2 Frame::offset() const
{
	return int2(m_extends.x, m_extends.y);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
